package com.tony.demo.test.eventbus;

import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import androidx.annotation.NonNull;

/**
 * Copyright:
 * Created by TangJian on 2019/4/17.
 * Description:
 * Modified:
 */
@SuppressWarnings("unused")
public class MyEventBus {
    private static MyEventBus instance;
    private Map<Object, List<EventManager>> eventManagerMap;
    private Handler handler;
    private ExecutorService executorService;
    private static final Map<Class<?>, List<EventManager>> METHOD_CACHE = new ConcurrentHashMap<>();

    private static final int BRIDGE = 0x40;
    private static final int SYNTHETIC = 0x1000;
    //    private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
    private static final int MODIFIERS_IGNORE =
            Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC | Modifier.FINAL;

    private MyEventBus() {
        eventManagerMap = new HashMap<>();
        handler = new Handler(Looper.getMainLooper());
        executorService = Executors.newCachedThreadPool();
    }

    public static MyEventBus newInstance() {
        if (instance == null) {
            synchronized (MyEventBus.class) {
                if (instance == null) {
                    instance = new MyEventBus();
                }
            }
        }
        return instance;
    }

    public void registerEvent(Object event) {
        if (METHOD_CACHE.get(event.getClass()) != null) {
            eventManagerMap.put(event, METHOD_CACHE.get(event.getClass()));
            return;
        }

        List<EventManager> eventManagers = eventManagerMap.get(event);
        if (eventManagers == null) {
            eventManagers = findEventManagers(event);
        }
        eventManagerMap.put(event, eventManagers);
        METHOD_CACHE.put(event.getClass(), eventManagers);
    }

    private List<EventManager> findEventManagers(Object event) {
        List<EventManager> eventManagers = new ArrayList<>();
        Method[] declaredMethods = event.getClass().getDeclaredMethods();
//        event.getClass().getMethods(); //该方法会获取到本对象的方法以及其所有父类的方法
        if (declaredMethods != null && declaredMethods.length > 0) {
            for (Method method : declaredMethods) {
                //获取方法的限定符
                int modifiers = method.getModifiers();
                //满足要求，public并且没有abstract、static、bridge、synthetic这些限定符
                if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                    Subscribe annotation = method.getAnnotation(Subscribe.class);
                    if (annotation != null) {
                        Type genericReturnType = method.getGenericReturnType();
                        if (!genericReturnType.toString().equals("void")) {
                            throw new RuntimeException("方法返回类型必须是void");
                        }
//
//                    Type[] genericParameterTypes = method.getGenericParameterTypes();
//                    if (genericParameterTypes == null || genericParameterTypes.length != 1) {
//                        throw new IllegalArgumentException("方法参数个数有且只能有一个");
//                    }

                        Class<?>[] parameterTypes = method.getParameterTypes();
//                    if (genericParameterTypes == null || genericParameterTypes.length != 1) {
//                        throw new IllegalArgumentException("方法参数个数有且只能有一个");
//                    }
                        EventManager eventManager = new EventManager(
                                parameterTypes, annotation.threadMode(), method, annotation.eventName());
                        eventManagers.add(eventManager);
                    }
                }
            }
        }
        return eventManagers;
    }

    /**
     * 缺陷： 1、在调用该方法的地方，需要知道将来接收事件的方法名称
     * 2、如果用户定义了名称不一样的但参数一样的接收事件的方法，
     * 并且在eventName注解使用了相同的名字，那么这样的两个或多个方法都会被执行
     *
     * @param eventName 事件名称
     * @param object    真正处理事件的那个方法的参数数组
     *                  <p>
     *                  Example one:  @Subscribe(threadMode = ThreadMode.MAIN, eventName = "dealMessage")
     *                  public void dealMessage(UserInfo userInfo) {
     *                  showToast("嘻嘻嘻嘻嘻");
     *                  }
     *                  Example two:  @Subscribe(threadMode = ThreadMode.MAIN, eventName = "dealEventTwo")
     *                  public void dealEventTwo(UserInfo userInfo) {
     *                  DMLog.e(this.getClass().getSimpleName(), "dealEventTwo");
     *                  }
     *                  Example three:  @Subscribe(threadMode = ThreadMode.MAIN, eventName = "dealEventTwo")
     *                  public void dealEventTwo() {
     *                  DMLog.e(this.getClass().getSimpleName(), "dealEventTwo without parameters");
     *                  }
     */
    public void postEvent(@NonNull String eventName, final Object... object) {
//        if (object == null || object.length == 0) {
//            throw new IllegalArgumentException("方法参数个数必须大于1个");
//        }
        if (TextUtils.isEmpty(eventName)) {
            throw new IllegalArgumentException("方法名称不能为null");
        }

        Set<Object> eventSet = eventManagerMap.keySet();  //得到注册事件对象的Set集合
        for (final Object event : eventSet) {
            //通过事件对象的Key值获取该对象下所有注册的事件List
            List<EventManager> eventManagers = eventManagerMap.get(event);
            if (eventManagers != null && eventManagers.size() > 0) {
                for (final EventManager eventManager : eventManagers) {
                    Type[] parameterTypes = eventManager.getMethod().getGenericParameterTypes();

                    boolean canInvokeMethod = false;
                    if (parameterTypes.length == 0 && object == null) {
                        canInvokeMethod = true;
                    } else if (object != null) {
                        //还需要增加判断响应事件的方法的修饰符是否public。
                        //只有public的方法，才能随意的被其他对象调用

                        //判断方法参数数量是否相同
                        if (parameterTypes.length == object.length) {
                            int length = parameterTypes.length;
                            boolean isMethodsSame = true;  //判断每个位置的方法参数类型是否一样
                            for (int i = 0; i < length; i++) {
                                if (object[i] != null &&
                                        !parameterTypes[i].toString().equals(object[i].getClass().toString())) {
                                    isMethodsSame = false;
                                    break;
                                }
                            }

                            //判断方法名字是否相同
                            if (isMethodsSame && eventManager.getEventName().equals(eventName)) {
                                canInvokeMethod = true;
                            }
                        }
                    }

                    if (canInvokeMethod) {
                        //切换方法执行线程
                        switchThread(eventManager, event, object);
                    }
                }
            }
        }
    }

    private void switchThread(final EventManager eventManager, final Object event, final Object... object) {
        switch (eventManager.getThreadMode()) {
            case MAIN:
                if (Looper.myLooper() == Looper.getMainLooper()) {
                    invokeMethod(event, eventManager, object);
                } else {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            invokeMethod(event, eventManager, object);
                        }
                    });
                }
                break;
            case POSTING:
                invokeMethod(event, eventManager, object);
                break;
            case BACKGROUND:
                if (Looper.myLooper() == Looper.getMainLooper()) {
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            invokeMethod(event, eventManager, object);
                        }
                    });
                } else {
                    invokeMethod(event, eventManager, object);
                }
                break;
            default:
                break;
        }
    }

    private void invokeMethod(Object event, EventManager eventManager, Object... object) {
        try {
            eventManager.getMethod().invoke(event, object);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    public void unRegisterEvent(Object event) {
        if (null != eventManagerMap && eventManagerMap.get(event) != null) {
            eventManagerMap.remove(event);
        }
    }
}
